package org.yihang.db;

import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.sql.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.Date;
import java.util.stream.Collectors;

/**
 * 结果集每一列的处理器
 *
 * @author yihang
 */
public interface ColumnHandler {


    /**
     * 将结果集({@link java.sql.ResultSet})中某一列的类型转换为java类型
     *
     * @param rs    结果集对象
     * @param index 列下标
     * @return 转换后的结果
     * @throws SQLException 可能出现的sql异常
     */
    Object toJava(ResultSet rs, int index) throws SQLException;

    /**
     * 将java对象转换为jdbc({@link java.sql.PreparedStatement})需要的类型
     *
     * @param psmt  预编译Statement对象
     * @param obj   java类型
     * @param index 下标
     * @throws SQLException 可能出现的sql异常
     */
    void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException;


    enum ColumnHandlers implements ColumnHandler {
        IntHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return rs.getInt(index);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                psmt.setObject(index, obj);
            }
        },
        ShortHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return rs.getShort(index);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                psmt.setObject(index, obj);
            }
        },
        ByteHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return rs.getByte(index);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                psmt.setObject(index, obj);
            }
        },
        LongHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return rs.getLong(index);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                psmt.setObject(index, obj);
            }
        },
        FloatHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return rs.getFloat(index);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                psmt.setObject(index, obj);
            }
        },
        DoubleHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return rs.getDouble(index);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                psmt.setObject(index, obj);
            }
        },
        BooleanHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return rs.getBoolean(index);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                psmt.setObject(index, obj);
            }
        },
        StringHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return rs.getString(index);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                psmt.setObject(index, obj);
            }
        },
        BigDecimalHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return rs.getBigDecimal(index);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                psmt.setObject(index, obj);
            }
        },
        LocalDateHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return rs.getObject(index, LocalDate.class);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                psmt.setObject(index, obj);
            }
        },
        LocalTimeHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return rs.getObject(index, LocalTime.class);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                psmt.setObject(index, obj);
            }
        },
        LocalDateTimeHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return rs.getObject(index, LocalDateTime.class);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                psmt.setObject(index, obj);
            }
        },
        DateHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                Timestamp timestamp = rs.getTimestamp(index);
                if(timestamp == null) {
                    return null;
                }
                return new Date(timestamp.getTime());
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                java.util.Date d = (java.util.Date) obj;
                psmt.setTimestamp(index, new Timestamp(d.getTime()));
            }
        },
        IntArrayHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return toArray(Integer.class, rs, index);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                if (obj == null) {
                    psmt.setObject(index, null, JDBCType.VARCHAR);
                } else {
                    String str = Arrays.stream(((Integer[]) obj)).map(String::valueOf).collect(Collectors.joining(","));
                    psmt.setString(index, str);
                }
            }
        },
        ShortArrayHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return toArray(Short.class, rs, index);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                if (obj == null) {
                    psmt.setObject(index, null, JDBCType.VARCHAR);
                } else {
                    String str = Arrays.stream(((Short[]) obj)).map(String::valueOf).collect(Collectors.joining(","));
                    psmt.setString(index, str);
                }
            }
        },
        ByteArrayHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return toArray(Byte.class, rs, index);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                if (obj == null) {
                    psmt.setObject(index, null, JDBCType.VARCHAR);
                } else {
                    String str = Arrays.stream(((Byte[]) obj)).map(String::valueOf).collect(Collectors.joining(","));
                    psmt.setString(index, str);
                }
            }
        },
        LongArrayHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return toArray(Long.class, rs, index);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                if (obj == null) {
                    psmt.setObject(index, null, JDBCType.VARCHAR);
                } else {
                    String str = Arrays.stream(((Long[]) obj)).map(String::valueOf).collect(Collectors.joining(","));
                    psmt.setString(index, str);
                }
            }
        },
        FloatArrayHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return toArray(Float.class, rs, index);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                if (obj == null) {
                    psmt.setObject(index, null, JDBCType.VARCHAR);
                } else {
                    String str = Arrays.stream(((Float[]) obj)).map(String::valueOf).collect(Collectors.joining(","));
                    psmt.setString(index, str);
                }
            }
        },
        DoubleArrayHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return toArray(Double.class, rs, index);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                if (obj == null) {
                    psmt.setObject(index, null, JDBCType.VARCHAR);
                } else {
                    String str = Arrays.stream(((Double[]) obj)).map(String::valueOf).collect(Collectors.joining(","));
                    psmt.setString(index, str);
                }
            }
        },
        BooleanArrayHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return toArray(Boolean.class, rs, index);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                if (obj == null) {
                    psmt.setObject(index, null, JDBCType.VARCHAR);
                } else {
                    String str = Arrays.stream(((Boolean[]) obj)).map(String::valueOf).collect(Collectors.joining(","));
                    psmt.setString(index, str);
                }
            }
        },
        StringArrayHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return toArray(String.class, rs, index);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                if (obj == null) {
                    psmt.setObject(index, null, JDBCType.VARCHAR);
                } else {
                    String str = Arrays.stream(((String[]) obj)).collect(Collectors.joining(","));
                    psmt.setString(index, str);
                }
            }
        },
        BigDecimalArrayHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return toArray(BigDecimal.class, rs, index);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                if (obj == null) {
                    psmt.setObject(index, null, JDBCType.VARCHAR);
                } else {
                    String str = Arrays.stream(((BigDecimal[]) obj)).map(BigDecimal::toString).collect(Collectors.joining(","));
                    psmt.setString(index, str);
                }
            }
        },
        LocalDateArrayHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return toArray(LocalDate.class, rs, index);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                if (obj == null) {
                    psmt.setObject(index, null, JDBCType.VARCHAR);
                } else {
                    String str = Arrays.stream(((LocalDate[]) obj)).map(LocalDate::toString).collect(Collectors.joining(","));
                    psmt.setString(index, str);
                }
            }
        },
        LocalTimeArrayHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return toArray(LocalTime.class, rs, index);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                if (obj == null) {
                    psmt.setObject(index, null, JDBCType.VARCHAR);
                } else {
                    String str = Arrays.stream(((LocalTime[]) obj)).map(LocalTime::toString).collect(Collectors.joining(","));
                    psmt.setString(index, str);
                }
            }
        },
        LocalDateTimeArrayHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return toArray(LocalDateTime.class, rs, index);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                if (obj == null) {
                    psmt.setObject(index, null, JDBCType.VARCHAR);
                } else {
                    String str = Arrays.stream(((LocalDateTime[]) obj)).map(Converters.DATE_TIME_FORMATTER::format).collect(Collectors.joining(","));
                    psmt.setString(index, str);
                }
            }
        },
        DateArrayHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return toArray(Date.class, rs, index);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                if (obj == null) {
                    psmt.setObject(index, null, JDBCType.VARCHAR);
                } else {
                    String str = Arrays.stream(((Date[]) obj)).map(d -> new SimpleDateFormat(Converters.DATE_TIME_PATTERN).format(d)).collect(Collectors.joining(","));
                    psmt.setString(index, str);
                }
            }
        },
        DefaultHandler {
            @Override
            public Object toJava(ResultSet rs, int index) throws SQLException {
                return rs.getObject(index);
            }

            @Override
            public void toJdbc(PreparedStatement psmt, Object obj, int index) throws SQLException {
                psmt.setObject(index, obj, JDBCType.VARCHAR);
            }
        };

        /**
         * 所有处理器集合，key为待处理类型，value为处理器
         */
        private static final Map<Class<?>, ColumnHandler> ALL_HANDLERS = new HashMap<>(64);
        /**
         * 能够支持的所有类型
         */
        private static final Set<Class<?>> SUPPORT_TYPES = new HashSet<>();

        /**
         * 结果集处理每一列
         *
         * @param clazz 待转换的类型
         * @param rs    结果集
         * @param index 列下标
         * @return 转换结果
         * @throws SQLException 可能出现的jdbc异常
         */
        static Object process(Class<?> clazz, ResultSet rs, int index) throws SQLException {
            rs.getObject(index);
            if (!clazz.isPrimitive() && rs.wasNull() && !clazz.isArray()) {
                return null;
            }
            return getHandler(clazz).toJava(rs, index);
        }

        /**
         * 获取某一class的处理器
         *
         * @param clazz 待处理类型
         * @return 处理器
         */
        static ColumnHandler getHandler(Class<?> clazz) {
            return ALL_HANDLERS.getOrDefault(clazz, DefaultHandler);
        }

        /**
         * 为某一class注册处理器
         *
         * @param clazz   要注册的类型
         * @param handler 处理器
         */
        static void registerHandler(Class<?> clazz, ColumnHandler handler) {
            ALL_HANDLERS.put(clazz, handler);
            SUPPORT_TYPES.add(clazz);
        }

        /**
         * 检查是否能够支持某一类型的处理
         *
         * @param clazz 类型
         * @return 是否能处理该类型
         */
        static boolean support(Class<?> clazz) {
            return SUPPORT_TYPES.contains(clazz);
        }

        /**
         * 初始化所有默认提供的处理器
         */
        static void init() {
            ALL_HANDLERS.put(int.class, IntHandler);
            ALL_HANDLERS.put(Integer.class, IntHandler);
            ALL_HANDLERS.put(short.class, ShortHandler);
            ALL_HANDLERS.put(Short.class, ShortHandler);
            ALL_HANDLERS.put(byte.class, ByteHandler);
            ALL_HANDLERS.put(Byte.class, ByteHandler);
            ALL_HANDLERS.put(long.class, LongHandler);
            ALL_HANDLERS.put(Long.class, LongHandler);
            ALL_HANDLERS.put(float.class, FloatHandler);
            ALL_HANDLERS.put(Float.class, FloatHandler);
            ALL_HANDLERS.put(double.class, DoubleHandler);
            ALL_HANDLERS.put(Double.class, DoubleHandler);
            ALL_HANDLERS.put(boolean.class, BooleanHandler);
            ALL_HANDLERS.put(Boolean.class, BooleanHandler);
            ALL_HANDLERS.put(String.class, StringHandler);
            ALL_HANDLERS.put(BigDecimal.class, BigDecimalHandler);
            ALL_HANDLERS.put(LocalDate.class, LocalDateHandler);
            ALL_HANDLERS.put(LocalTime.class, LocalTimeHandler);
            ALL_HANDLERS.put(LocalDateTime.class, LocalDateTimeHandler);
            ALL_HANDLERS.put(Date.class, DateHandler);
            ALL_HANDLERS.put(Integer[].class, IntArrayHandler);
            ALL_HANDLERS.put(Short[].class, ShortArrayHandler);
            ALL_HANDLERS.put(Byte[].class, ByteArrayHandler);
            ALL_HANDLERS.put(Long[].class, LongArrayHandler);
            ALL_HANDLERS.put(Float[].class, FloatArrayHandler);
            ALL_HANDLERS.put(Double[].class, DoubleArrayHandler);
            ALL_HANDLERS.put(Boolean[].class, BooleanArrayHandler);
            ALL_HANDLERS.put(String[].class, StringArrayHandler);
            ALL_HANDLERS.put(BigDecimal[].class, BigDecimalArrayHandler);
            ALL_HANDLERS.put(LocalDate[].class, LocalDateArrayHandler);
            ALL_HANDLERS.put(LocalTime[].class, LocalTimeArrayHandler);
            ALL_HANDLERS.put(LocalDateTime[].class, LocalDateTimeArrayHandler);
            ALL_HANDLERS.put(Date[].class, DateArrayHandler);
            SUPPORT_TYPES.addAll(ALL_HANDLERS.keySet());
        }

        <T> T[] toArray(Class<T> clazz, ResultSet rs, int index) throws SQLException {
            String str = rs.getString(index);
            if (str == null) {
                return (T[]) Array.newInstance(clazz, 0);
            }
            String[] split = str.split(",");
            if (split.length == 0) {
                return (T[]) Array.newInstance(clazz, 0);
            }
            return Arrays.stream(split).map(s -> Converters.getConverter(clazz).toObject(s))
                    .collect(Collectors.toList()).toArray((T[]) Array.newInstance(clazz, split.length));
        }

    }

    enum Converters implements Converter {

        IntConverter {
            @Override
            public Object toObject(String str) {
                return Integer.valueOf(str);
            }
        },
        ShortConverter {
            @Override
            public Object toObject(String str) {
                return Short.valueOf(str);
            }
        },
        ByteConverter {
            @Override
            public Object toObject(String str) {
                return Byte.valueOf(str);
            }
        },
        LongConverter {
            @Override
            public Object toObject(String str) {
                return Long.valueOf(str);
            }
        },
        FloatConverter {
            @Override
            public Object toObject(String str) {
                return Float.valueOf(str);
            }
        },
        DoubleConverter {
            @Override
            public Object toObject(String str) {
                return Double.valueOf(str);
            }
        },
        BooleanConverter {
            @Override
            public Object toObject(String str) {
                return Boolean.valueOf(str);
            }
        },
        BigDecimalConverter {
            @Override
            public Object toObject(String str) {
                return new BigDecimal(str);
            }
        },
        StringConverter {
            @Override
            public Object toObject(String str) {
                return str;
            }
        },
        LocalDateConverter {
            @Override
            public Object toObject(String str) {
                return LocalDate.parse(str);
            }
        },
        LocalTimeConverter {
            @Override
            public Object toObject(String str) {
                return LocalTime.parse(str);
            }
        },
        LocalDateTimeConverter {
            @Override
            public Object toObject(String str) {
                return LocalDateTime.parse(str, DATE_TIME_FORMATTER);
            }
        },
        DateConverter {
            @Override
            public Object toObject(String str) {
                try {
                    SimpleDateFormat sdf = new SimpleDateFormat(DATE_TIME_PATTERN);
                    return sdf.parse(str);
                } catch (ParseException e) {
                    throw new RuntimeException(e);
                }
            }
        };
        /**
         * 默认的日期时间格式
         */
        static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
        /**
         * 日期时间Formatter类，因为线程安全，可以设置为全局变量
         */
        static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN);
        /**
         * 所有的转换器集合，key为类型，值为转换器
         */
        private static final Map<Class<?>, Converter> ALL_CONVERTERS = new HashMap<>(64);

        /**
         * 根据类型获取转换器
         *
         * @param clazz 类型
         * @return 转换器
         */
        static Converter getConverter(Class<?> clazz) {
            return ALL_CONVERTERS.get(clazz);
        }

        /**
         * 扩展转换器，可以为新的类型添加新的转换器
         *
         * @param clazz     类型
         * @param converter 转换器
         */
        static void registerConverter(Class<?> clazz, Converter converter) {
            ALL_CONVERTERS.put(clazz, converter);
        }

        /**
         * 初始化所有默认提供的转换器
         */
        static void init() {
            ALL_CONVERTERS.put(int.class, IntConverter);
            ALL_CONVERTERS.put(Integer.class, IntConverter);
            ALL_CONVERTERS.put(short.class, ShortConverter);
            ALL_CONVERTERS.put(Short.class, ShortConverter);
            ALL_CONVERTERS.put(byte.class, ByteConverter);
            ALL_CONVERTERS.put(Byte.class, ByteConverter);
            ALL_CONVERTERS.put(long.class, LongConverter);
            ALL_CONVERTERS.put(Long.class, LongConverter);
            ALL_CONVERTERS.put(float.class, FloatConverter);
            ALL_CONVERTERS.put(Float.class, FloatConverter);
            ALL_CONVERTERS.put(double.class, DoubleConverter);
            ALL_CONVERTERS.put(Double.class, DoubleConverter);
            ALL_CONVERTERS.put(boolean.class, BooleanConverter);
            ALL_CONVERTERS.put(Boolean.class, BooleanConverter);
            ALL_CONVERTERS.put(String.class, StringConverter);
            ALL_CONVERTERS.put(BigDecimal.class, BigDecimalConverter);
            ALL_CONVERTERS.put(LocalDate.class, LocalDateConverter);
            ALL_CONVERTERS.put(LocalTime.class, LocalTimeConverter);
            ALL_CONVERTERS.put(LocalDateTime.class, LocalDateTimeConverter);
            ALL_CONVERTERS.put(Date.class, DateConverter);
        }
    }

    interface Converter {


        /**
         * 转换方法，负责把String转换为各种需要的类型
         *
         * @param str 待转换的String
         * @return 转换结果
         */
        Object toObject(String str);


    }
}
